package cn.wizzer.app.crown.modules.services.impl;

import cn.hutool.core.util.RandomUtil;
import cn.wizzer.framework.base.exceptions.BizException;
import cn.wizzer.framework.util.DateUtil;
import cn.wizzer.app.crown.commons.util.DecipherUtil;
import cn.wizzer.app.crown.commons.websocket.CrawlerWebSocketClient;
import cn.wizzer.app.crown.modules.models.CrownBet;
import cn.wizzer.app.crown.modules.models.CrownMatch;
import cn.wizzer.app.crown.modules.models.constant.CrownConstant;
import cn.wizzer.app.crown.modules.models.constant.Fields;
import cn.wizzer.app.crown.modules.models.enums.BetsEnum;
import cn.wizzer.app.crown.modules.models.enums.MatchStageEnum;
import cn.wizzer.app.crown.modules.models.vo.CrownBetVO;
import cn.wizzer.app.crown.modules.services.CrownBetService;
import cn.wizzer.app.crown.modules.services.CrownMatchService;
import cn.wizzer.app.crown.modules.services.result.MatchResultService;
import cn.wizzer.app.sys.modules.models.SysParam;
import cn.wizzer.app.sys.modules.services.SysParamService;
import cn.wizzer.framework.base.model.enums.BizExceptionEnum;
import cn.wizzer.framework.base.service.BaseServiceImpl;
import cn.wizzer.framework.base.service.CommonDBService;
import cn.wizzer.framework.page.Pagination;
import cn.wizzer.framework.util.JSONUtil;
import cn.wizzer.framework.util.Result;
import com.alibaba.dubbo.common.utils.CollectionUtils;
import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.dubbo.config.annotation.Service;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.cri.SqlExpressionGroup;
import org.nutz.integration.jedis.RedisService;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Strings;
import org.nutz.lang.Times;
import org.nutz.log.Log;
import org.nutz.log.Logs;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;

@IocBean(args = {"refer:dao"})
@Service(interfaceClass = CrownMatchService.class)
public class CrownMatchServiceImpl extends BaseServiceImpl<CrownMatch> implements CrownMatchService {

    public CrownMatchServiceImpl(Dao dao) {
        super(dao);
    }

    @Inject
    private RedisService redisService;
    @Inject
    private CrownBetService betService;
    @Inject
    private CommonDBService dbService;
    @Inject
    private SysParamService sysParamService;
    @Inject
    @Reference(retries = 3, timeout = 500000)
    private MatchResultService ftMatchResultService;

    private static final Log log = Logs.get();


    @Override
    public Pagination listPage(String searchKeyword, String stage, Date start, Date end, int pageNumber, int pageSize) {
        Cnd cnd = Cnd.NEW();
        if (start != null) {
            cnd.and("dateTime", ">=", start);
        }
        if (end != null) {
            cnd.and("dateTime", "<=", end);
        }
        if (Strings.isNotBlank(stage)) {
            cnd.and("stage", "=", stage);
        }
        if (Strings.isNotBlank(searchKeyword)) {
            SqlExpressionGroup e1 = Cnd.exps("home", "like", "%" + searchKeyword + "%");
            SqlExpressionGroup e2 = Cnd.exps("custom", "like", "%" + searchKeyword + "%");
            SqlExpressionGroup e3 = Cnd.exps("league", "like", "%" + searchKeyword + "%");
            SqlExpressionGroup e4 = Cnd.exps("mid", "=", searchKeyword);
            cnd.and(e1.or(e2).or(e3).or(e4));
        }
        return this.listPage(pageNumber, pageSize, cnd.orderBy("dateTime", "desc"));
    }


    @Override
    public CrownMatch queryByTeamInfo(String league, String home, String custom, Date dateTime) {
        Cnd cnd = Cnd.where("league", "=", league).and("home", "=", home)
                .and("custom", "=", custom).and("dateTime", "=", dateTime);
        return this.fetch(cnd);
    }

    @Override
    public List<CrownBetVO> hotBets(String timezone) {
        // 后日零点
        long afterZeroLong = DateUtil.getZeroLong(+2, timezone);
        String afterZeroStr = DateUtil.sysConvertEdt(Times.ts2D(afterZeroLong));
        // 当前时间转美东时间
        String nowStr = DateUtil.sysConvertEdt(Times.now());
        // 获取当天利率最高的10条 按利率倒序排序
        Sql betSql = Sqls.create("SELECT b.betContent, b.type, b.recruitAmount, b.completedAmount,b.rate,")
                .appendSourceSql("m.id, m.league, m.home, m.custom, m.dateTime, b.protect")
                .appendSourceSql("FROM crown_bet AS b");
        Sql countSql = Sqls.create("SELECT COUNT(1) FROM crown_bet AS b");
        Sql joinSql = Sqls.create("LEFT JOIN crown_match AS m on b.matchId = m.id")
                .appendSourceSql("WHERE b.completedAmount < b.recruitAmount AND (m.dateTime > @timeNow AND m.dateTime < @timeAfter)")
                .appendSourceSql("AND m.stage!= 'finished' AND m.stage!= 'other'")
                .appendSourceSql("ORDER BY rate DESC")
                .setParam("timeNow", nowStr)
                .setParam("timeAfter", afterZeroStr);
        betSql.appendSourceSql(joinSql.toString());
        countSql.appendSourceSql(joinSql.toString());
        // 取10条
        Pagination pagination = dbService.listPage(CrownBetVO.class, 1, 10, betSql.toString(), countSql.toString());
        List<CrownBetVO> list = pagination.getList(CrownBetVO.class);
        // 根据最多完成金额排序
        list.sort(Comparator.comparing(CrownBet::getCompletedAmount).reversed());
        return list;
    }

    @Override
    public Pagination leagueList(MatchStageEnum stage, String timezone, int pageNo, int pageSize) {
        Pagination pagination = this.matchList(stage, null, timezone, pageNo, pageSize);
        List<CrownMatch> list = pagination.getList(CrownMatch.class);
        // 获取赛事的联赛名集合(去重)
        List<String> leagueList = list.stream().map(CrownMatch::getLeague).distinct().collect(Collectors.toList());
        pagination.setList(leagueList);
        pagination.setTotalCount(leagueList.size());
        return pagination;
    }

    @Override
    public Pagination matchList(MatchStageEnum stage, List<String> leagueList, String timezone, int pageNo, int pageSize) {
        Cnd cnd = Cnd.NEW();
        // 按联赛筛选
        if (CollectionUtils.isNotEmpty(leagueList)) {
            cnd.and("league", "in", leagueList);
        }
        // 当前时间转美东
        String nowStr = DateUtil.sysConvertEdt(Times.now());
        // 客户端明日零点
        long tomorrowZeroLong = DateUtil.getZeroLong(+1, timezone);
        String tomorrowZeroStr = DateUtil.sysConvertEdt(Times.ts2D(tomorrowZeroLong));
        // 客户端时间后日零点
        long afterZeroLong = DateUtil.getZeroLong(+2, timezone);
        String afterZeroStr = DateUtil.sysConvertEdt(Times.ts2D(afterZeroLong));
        // today
        if (MatchStageEnum.BROWSE.equals(stage)) {
            // 查出当前时间之后，明天零点之前的所有赛事
            cnd.and("dateTime", ">", nowStr).and("dateTime", "<", tomorrowZeroStr);
        }
        // tomorrow
        else if (MatchStageEnum.FUTURE.equals(stage)) {
            // 查出明天零点之后，后天零点之前的所有赛事
            cnd.and("dateTime", ">=", tomorrowZeroStr).and("dateTime", "<", afterZeroStr);
        }
        // ALL
        else {
            // 查出当前时间之后，后天零点之前的所有赛事
            cnd.and("dateTime", ">", nowStr).and("dateTime", "<", afterZeroStr);
        }
        cnd.and("stage", "!=", CrownConstant.OTHER).and("stage", "!=", CrownConstant.FINISHED);
        return this.listPage(pageNo, pageSize, cnd.asc("dateTime"));
    }


    @Override
    public CrownMatch composeMatch(BetsEnum betType, String matchId) {
        betType = null == betType ? BetsEnum.FULL_PD : betType;
        CrownMatch match = this.fetch(matchId);
        if (null == match) {
            Result.breakWith(BizExceptionEnum.MATCH_NOT_EXIST);
        }
        if (CrownConstant.FINISHED.equals(match.getStage()) || CrownConstant.OTHER.equals(match.getStage())) {
            Result.breakWith(BizExceptionEnum.MATCH_IS_OVER);
        }
        List<CrownBet> bets = betService.queryBets(match.getId(), betType, "betContent");
        // 可交易量(招募金额 - 已完成金额)
        bets.forEach(bet -> {
            // 计算‘可交易量’
            if (null != bet.getCompletedAmount() && null != bet.getRecruitAmount()) {
                BigDecimal quota = bet.getRecruitAmount().subtract(bet.getCompletedAmount());
                // 如果可交易量大于0
                if (quota.compareTo(BigDecimal.ZERO) > 0) {
                    bet.setQuota(quota);
                } else {
                    bet.setQuota(BigDecimal.ZERO);
                }
            }
        });
        // 累加‘completedAmount’
        BigDecimal totalAmount = BigDecimal.ZERO;
        if (bets.size() > 0) {
            totalAmount = bets.stream().map(CrownBet::getCompletedAmount).reduce(BigDecimal::add).get();
        }
        match.setBets(bets);
        match.setTotalAmount(totalAmount);
        return match;
    }


    @Override
    public Pagination resultList(String timezone, Long start, Long end, int pageNo, int pageSize) {
        Sql sql = Sqls.create("select * from crown_match $condition ");
        Sql countSql = Sqls.create("select count(1) from crown_match $condition ");
        Cnd cnd = Cnd.where(Cnd.exps("stage", "=", CrownConstant.FINISHED)
                .or("stage", "=", CrownConstant.OTHER));
        // 如果传了时间，按时间区间查询
        if (null != start && null != end) {
            // 客户端转美东时间
            String startTime = DateUtil.sysConvertEdt(Times.ts2D(start));
            String endTime = DateUtil.sysConvertEdt(Times.ts2D(end));
            cnd.and("dateTime", ">=", startTime).and("dateTime", "<=", endTime);
        }
        sql.setCondition(cnd.desc("dateTime"));
        countSql.setCondition(cnd.desc("dateTime"));
        return this.listPage(pageNo, pageSize, sql, countSql);
    }


    @Override
    public CrownMatch result(String id) {
        return this.fetch(id);
    }


    @Override
    public void crawlMatchResult(Long startTime, Long endTime) {
        SysParam sysParam = sysParamService.fetch();
        // 一天的毫秒值
        long oneDayMilliSecond = 24 * 60 * 60 * 1000;
        // 计算要爬取的天数
        long dayNum = (endTime - startTime) / oneDayMilliSecond + 1;
        // 如果不限制天数爬虫程序可能会超时
        if (dayNum > 2) {
            Result.breakWith(BizExceptionEnum.CHOOSE_ONE_DAY_TO_CRAWLING);
        }
        // 处理日期格式
        String start = DateFormatUtils.format(startTime, "yyyy-MM-dd HH:mm:ss");
        String end = DateFormatUtils.format(endTime, "yyyy-MM-dd HH:mm:ss");
        // 查询日期范围内未结束赛事的mid
        Cnd cnd = Cnd.NEW();
        cnd.where().andBetween("dateTime", start, end)
                .and("stage", "!=", "finished").and("stage", "!=", "other");
        List<String> mids = this.query("mid", cnd).stream().map(CrownMatch::getMid).collect(Collectors.toList());

        log.info("手动获取赛果,mid集合:" + mids + "");
        CrawlerWebSocketClient.instance().send("/crown/match/result?no=" + sysParam.getCrawlerNo() + "&data=" + DecipherUtil.encrypt(sysParam.getCrawlerSecret(),
                "mids=" + JSONUtil.toJSONString(mids)));
    }


    @Override
    public List matchCount(String timezone) {
        Pagination pagination;
        pagination = this.matchList(MatchStageEnum.BROWSE, null, timezone, 1, 500);
        // 过滤后集合size是BROWSE的数量
        int broseCount = pagination.getList().size();
        pagination = this.matchList(MatchStageEnum.FUTURE, null, timezone, 1, 500);
        int futureCount = pagination.getList().size();

        List<Map> result = new ArrayList<>();
        result.add(
                Result.createMap(
                        Fields.MATCH_COUNT_FIELD_STAGE, MatchStageEnum.BROWSE,
                        Fields.MATCH_COUNT_FIELD_COUNT, broseCount
                )
        );
        result.add(
                Result.createMap(
                        Fields.MATCH_COUNT_FIELD_STAGE, MatchStageEnum.FUTURE,
                        Fields.MATCH_COUNT_FIELD_COUNT, futureCount
                )
        );
        result.add(
                Result.createMap(
                        Fields.MATCH_COUNT_FIELD_STAGE, "All",
                        Fields.MATCH_COUNT_FIELD_COUNT, futureCount + broseCount
                )
        );
        return result;
    }


    @Override
    public void mockBets() {
        // 当前时间转美东
        String nowStr = DateUtil.sysConvertEdt(Times.now());
        List<CrownMatch> list = this.query(Cnd.where("dateTime", ">", nowStr));
        // 6小时以内 每次随机加百分之0.05-百分之10
        List<CrownMatch> collect = list.stream()
                .filter(match -> DateUtil.edtConvertSys(match.getDateTime()).getTime() - System.currentTimeMillis() < 6 * 3600 * 1000)
                .collect(Collectors.toList());

        this.handleMockBets(collect, RandomUtil.randomDouble(0.05, 1, 2, RoundingMode.DOWN));
        // 3小时以外 每次随机加百分之0.02——百分之0.5
        list.removeAll(collect);

        this.handleMockBets(list, RandomUtil.randomDouble(0.02, 0.5, 3, RoundingMode.DOWN));
    }

    @Override
    public void reconnect() {
        CrawlerWebSocketClient.instance().close(1);
    }


    /**
     * 处理赔率
     *
     * @param bet
     */
    @Override
    public void handleRate(CrownBet bet) {
        SysParam sysParam = sysParamService.fetch();
        // 获取计算公式(计算公式已经废弃，改为纯数值，例如50)
        String formula;
        switch (bet.getType()) {
            case FULL_PD:
                formula = sysParam.getFullPdFormula();
                break;
            case HALF_PD:
                formula = sysParam.getHalfPdFormula();
                break;
            case FULL_GOALS:
                formula = sysParam.getFullScoreFormula();
                break;
            default:
                log.error("转换利率bug，玩法不存在：" + bet.getType());
                throw new BizException("转换利率bug，玩法不存在：" + bet.getType());
        }
        // 系数
        BigDecimal ratio = new BigDecimal(formula);
        // 50 / odds
        BigDecimal rate = ratio.divide(bet.getOdds(), 2, BigDecimal.ROUND_HALF_UP);
        bet.setRate(rate);
//        ScriptEngine js = new ScriptEngineManager().getEngineByName("JavaScript");
//        try {
//            // 替换公式中的赔率字段名称rate
//            String s = formula.replaceAll("odds", bet.getOdds().toString());
//            // 执行计算 如50/6.3\
//            double eval = Double.parseDouble(js.eval(s).toString());
//            // double转BigDecimal四舍五入 赋值给rate
//            bet.setRate(new BigDecimal(eval).setScale(2, BigDecimal.ROUND_HALF_UP));
//        } catch (Exception e) {
//            log.error("爬虫时转换利率公式js文本解析错误,具体信息：" + e);
//        }
    }


    /**
     * 处理更新Bet
     *
     * @param matchList
     */
    private void handleMockBets(List<CrownMatch> matchList, double value) {
        // betContent随机集合
        ArrayList<String> randomBets = Lists.newArrayList("0", "1", "2", "3", "4", "6", "7", "r", "+");
        SysParam sysParam = sysParamService.fetch();
        // 招募金额额度
        BigDecimal recruitAmount = sysParam.getRecruitAmount();
        // 查询每场赛事各个玩法rate高于3的
        Sql betSql = Sqls.create("SELECT b.id,b.completedAmount")
                .appendSourceSql("FROM crown_bet AS b")
                .appendSourceSql("WHERE b.matchId = @matchId")
                .appendSourceSql("and b.rate > @highRate");
        for (CrownMatch match : matchList) {
            betSql.setParam("matchId", match.getId());
            // 如果赛事离开赛时间小于15分钟，把年化高的‘已完成金额’进度设置成90%以上
            String dateStr = DateUtil.sysConvertEdt(Times.nextMinute(Times.now(), +15));
            Date preNow = DateUtil.parseDate(dateStr, DateUtil.DATE_FORMAT_PATTERN.Y_M_D_H_M_S.PATTERN);
            if (preNow != null && preNow.getTime() >= match.getDateTime().getTime()) {
                // 获取年化高于5的
                betSql.setParam("highRate", 3);
                List<CrownBet> highBets = dbService.list(CrownBet.class, betSql.toString());
                highBets.forEach(bet -> {
                    // 如果‘已完成金额’占比小于90%，随机更新至百分之90到95之间
                    if (bet.getCompletedAmount().divide(recruitAmount, 2, RoundingMode.HALF_UP).compareTo(BigDecimal.valueOf(0.75)) < 0) {
                        double v = RandomUtil.randomDouble(0.75, 0.95, 2, RoundingMode.HALF_UP);
                        betService.update(Chain.make("completedAmount", recruitAmount.multiply(BigDecimal.valueOf(v))),
                                Cnd.where("id", "=", bet.getId()));
                    }
                });
            }
            // 其他时间赛事按参数累加更新
            else {
                // 获取年化高于5的首页热门更新频率加快
                betSql.setParam("highRate", 5);
                List<CrownBet> highBets = dbService.list(CrownBet.class, betSql.toString());
                for (CrownBet bet : highBets) {
                    // 阈值百分之75
                    Cnd cnd = Cnd.where("id", "=", bet.getId()).and("completedAmount / recruitAmount", "<", 0.75);
                    // 随机加100到3000$(recruitAmount为10万的百分之0.001到百分之3)
                    double v = RandomUtil.randomDouble(0.001, value, 3, RoundingMode.DOWN);
                    betService.update(Chain.makeSpecial("completedAmount", "+" + recruitAmount.multiply(BigDecimal.valueOf(v))), cnd);
                }
                // 普通年化根据赛事随机更新
                Cnd cnd = Cnd.where("matchId", "=", match.getId())
                        .and("type", "=", BetsEnum.randomEnum())
                        .and("betContent", "like", "%" + randomBets.get(RandomUtil.randomInt(randomBets.size())))
                        .and("completedAmount", "=", 0)
                        .and("completedAmount / recruitAmount", "<", 0.75);
                int count = betService.update(Chain.makeSpecial("completedAmount", "+" + recruitAmount.multiply(BigDecimal.valueOf(value))), cnd);
                if (count == 0) {
                    cnd.and("completedAmount", ">", 0);
                    betService.update(Chain.makeSpecial("completedAmount", "+" + recruitAmount.multiply(BigDecimal.valueOf(value))), cnd);
                }
            }
            // 删除bets缓存
            betService.clearCache(match.getId());
        }
    }
}
